﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Windows.Forms;
using System.ComponentModel;
using System.Threading;
using System.Web.Script.Serialization;
using System.Web;

namespace Comet
{
    class ChatHandler
    {
        public class Chat
        {
            public enum Gender { Anyone, Male, Female }
            public string ChatID { get; set; }
            public string PartnerID = null;
            public bool Connected = true;
            public Gender UserGender { get; set; }
            public Gender ChatWith { get; set; }
            public List<ChatNotification> Notifications = new List<ChatNotification>();
            public HttpListenerContext EventHandle = null;
            public DateTime TimeStamp = DateTime.Now;
            public int MessageCount { get; set; }
            public bool DisconnectHandled = false;
        }

        public class ChatNotification
        {
            public enum NotificationType { Connected, Typing, Message }
            public NotificationType Type { get; set; }
            public string Data { get; set; }
        }

        public string[] chatHandlerMethods = new string[] { "events", "start", "send", "typing", "disconnect" };
        public List<Chat> Chats = new List<Chat>();
        bool chatsBusy = false;
        
        System.Windows.Forms.Timer updaterFixer = new System.Windows.Forms.Timer();

        BackgroundWorker chatManager = new BackgroundWorker();

        public ChatHandler()
        {
            
            chatManager.DoWork += new DoWorkEventHandler(chatManager_DoWork);
            chatManager.RunWorkerAsync();
            chatManager.RunWorkerCompleted += new RunWorkerCompletedEventHandler(chatManager_RunWorkerCompleted);

            updaterFixer.Tick += new EventHandler(updaterFixer_Tick);
            updaterFixer.Interval = 1000;
            updaterFixer.Start();
        }

        void updaterFixer_Tick(object sender, EventArgs e)
        {
            if (!chatManager.IsBusy)
                chatManager.RunWorkerAsync();
        }

        void chatManager_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            chatManager.RunWorkerAsync();
        }

        public void HandleMethod(string method, HttpListenerContext context)
        {
            Dictionary<string, string> postVars;
            Chat chat;

            if (method == "start")
            {
                handleStart(context);
            }
            else if ((postVars = postVars = Communications.ParsePost(context)) != null && postVars.ContainsKey("id") && (chat = FindChat(postVars["id"])) != null)
            {
                switch (method)
                {
                    case "events":
                        handleEvents(context, chat);
                        break;
                    case "send":
                        handleSend(chat, postVars, context);
                        break;
                    case "typing":
                        handleTyping(chat, postVars);
                        break;
                    case "disconnect":
                        handleDisconnect(chat);
                        break;
                }
            }
            if (method != "events") Communications.ReplyCompleted(context);
        }

        void chatManager_DoWork(object sender, DoWorkEventArgs e)
        {
            while (true)
            {
                chatsBusy = true;

                Stats.ChatStats.OnlineChats = 0;
                Stats.ChatStats.OnlineAnyones = 0;
                Stats.ChatStats.OnlineMalesToFemales = 0;
                Stats.ChatStats.OnlineMalesToMales = 0;
                Stats.ChatStats.OnlineFemalesToMales = 0;
                Stats.ChatStats.OnlineFemalesToFemales = 0;

                //Pairing
                int chatsLength = Chats.Count - 1;
                //for (int i = 0; i <= Chats.Count - 1; i++)
                foreach(Chat chat in Chats)
                {
                    //Chat chat = Chats[chatsLength--];
                    Chat partner;
                    if (chat != null && chat.PartnerID == null && chat.Connected == true && (partner = FindLooseChat(chat.ChatID, chat.ChatWith, chat.UserGender)) != null)
                    {
                        chat.PartnerID = partner.ChatID;
                        partner.PartnerID = chat.ChatID;

                        chat.Notifications.Add(new ChatNotification { Type = ChatNotification.NotificationType.Connected, Data = "1" });
                        partner.Notifications.Add(new ChatNotification { Type = ChatNotification.NotificationType.Connected, Data = "1" });
                    }
                    switch (chat.UserGender)
                    {
                        case Chat.Gender.Anyone:
                            Stats.ChatStats.OnlineAnyones++;
                            break;
                        case Chat.Gender.Male:
                            switch (chat.ChatWith)
                            {
                                case Chat.Gender.Female:
                                    Stats.ChatStats.OnlineMalesToFemales++;
                                    break;
                                case Chat.Gender.Male:
                                    Stats.ChatStats.OnlineMalesToMales++;
                                    break;
                            }
                            break;
                        case Chat.Gender.Female:
                            switch (chat.ChatWith)
                            {
                                case Chat.Gender.Male:
                                    Stats.ChatStats.OnlineFemalesToMales++;
                                    break;
                                case Chat.Gender.Female:
                                    Stats.ChatStats.OnlineFemalesToFemales++;
                                    break;
                            }
                            break;
                    }

                }
                Stats.ChatStats.OnlineChats = Chats.Count;


                //Notifying
                foreach (Chat chat in Chats)
                {
                    if (chat != null)
                    {
                        if ((DateTime.Now - chat.TimeStamp).TotalSeconds > 60)
                        {
                            if (!chat.DisconnectHandled)
                            {
                                chat.DisconnectHandled = true;
                                chat.Connected = false;
                                ThreadPool.QueueUserWorkItem(handleDisconnect, chat);
                            }
                        }
                        if (chat.Notifications.Count > 0)
                        {
                            ThreadPool.QueueUserWorkItem(SendNotifications, chat);
                        }
                    }
                }

                Chats = Chats.Where(x => x.Connected).ToList();


                chatsBusy = false;
                Thread.Sleep(100);
            }
        }

        Chat FindLooseChat(string exclude, Chat.Gender gender, Chat.Gender chatWith)
        {
            Chat[] chats = Chats.ToArray();
            foreach (Chat chat in chats)
            {
                if (chat != null && chat.ChatID != exclude && chat.PartnerID == null && chat.Connected && chat.UserGender == gender && chat.ChatWith == chatWith)
                {
                    chats = null;
                    return chat;
                }
            }
            return null;
        }

        Chat FindChat(string chatID)
        {
            Chat[] chats = Chats.ToArray();
            foreach (Chat chat in chats)
            {
                if (chat != null && chat.ChatID == chatID)
                {
                    chats = null;
                    return chat;
                }
            }
            return null;
        }

        #region HandleMethods
        void handleStart(HttpListenerContext context)
        {
            try
            {

                if (Extra.CheckIP(context.Request.RemoteEndPoint.ToString().Split(':')[0]))
                {
                    Communications.SendResponse(context, Communications.DataType.Text, "You have been banned");
                    return;
                }

                Dictionary<string, string> postData = Communications.ParsePost(context);
                Chat.Gender gender = (Chat.Gender)Enum.Parse(typeof(Chat.Gender), postData["gender"], true);
                Chat.Gender chatwith = (Chat.Gender)Enum.Parse(typeof(Chat.Gender), postData["chatwith"], true);
                Chat chat = new Chat { ChatID = Extra.GenerateChatID, UserGender = gender, ChatWith = chatwith, Connected = true };

                Stats.ChatStats.AllChats++;
                switch (chat.UserGender)
                {
                    case Chat.Gender.Anyone:
                        Stats.ChatStats.AllAnyones++;
                        break;
                    case Chat.Gender.Male:
                        switch (chat.ChatWith)
                        {
                            case Chat.Gender.Female:
                                Stats.ChatStats.AllMalesToFemales++;
                                break;
                            case Chat.Gender.Male:
                                Stats.ChatStats.AllMalesToMales++;
                                break;
                        }
                        break;
                    case Chat.Gender.Female:
                        switch (chat.ChatWith)
                        {
                            case Chat.Gender.Male:
                                Stats.ChatStats.AllFemalesToMales++;
                                break;
                            case Chat.Gender.Female:
                                Stats.ChatStats.AllFemalesToFemales++;
                                break;
                        }
                        break;
                }

                WaitForBusyChats();
                //Chats.Insert(Extra.rand.Next(0, Chats.Count), chat);
                Chats.Add(chat);
                Communications.SendResponse(context, Communications.DataType.Text, chat.ChatID);
            }
            catch { }
        }

        void handleEvents(HttpListenerContext context, Chat chat)
        {
            context.Response.KeepAlive = true;
            chat.TimeStamp = DateTime.Now;
            if (chat.EventHandle != null) Communications.ReplyCompleted(chat.EventHandle);
            chat.EventHandle = context;
        }
        void handleSend(Chat chat, Dictionary<string, string> postVars, HttpListenerContext context)
        {
            chat.MessageCount++;
            Chat partner = FindChat(chat.PartnerID);
            if (partner != null)
            {
                string sentMessage = HttpUtility.UrlDecode(postVars["msg"]);
                if (Extra.FlagIP(sentMessage, context.Request.RemoteEndPoint.ToString()))
                {
                    Communications.SendResponse(context, Communications.DataType.Text, "You have been banned");
                    handleDisconnect(chat);
                    return;
                }
                partner.Notifications.Add(new ChatNotification { Type = ChatNotification.NotificationType.Message, Data = sentMessage });
                Stats.ChatStats.MessagesSent++;
            }
        }
        void handleTyping(Chat chat, Dictionary<string, string> postVars)
        {
            Chat parnter = FindChat(chat.PartnerID);
            if (parnter != null)
            {
                parnter.Notifications.Add(new ChatNotification { Type = ChatNotification.NotificationType.Typing, Data = postVars["msg"] });
            }
        }
        void handleDisconnect(Chat chat)
        {
            chat.Connected = false;
            if (chat.Notifications != null)
                chat.Notifications.Add(new ChatNotification { Type = ChatNotification.NotificationType.Connected, Data = "0" });

            Chat partner;
            if (chat.PartnerID != null && (partner = FindChat(chat.PartnerID)) != null)
            {
                WaitForBusyChats();
                partner.Connected = false;
                if (chat.Notifications != null)
                    partner.Notifications.Add(new ChatNotification { Type = ChatNotification.NotificationType.Connected, Data = "0" });
            }
        }
        void handleDisconnect(object chat)
        {
            handleDisconnect((Chat)chat);
        }

        #endregion

        void WaitForBusyChats()
        {
            while (chatsBusy) { Thread.Sleep(10); }
        }

        void SendNotifications(object chatObj)
        {
            Chat chat = (Chat)chatObj;
            if (chat.EventHandle != null)
            {

                ChatNotification[] notifications = chat.Notifications.ToArray();

                chat.Notifications.Clear();
                chat.Notifications = new List<ChatNotification>();

                foreach (ChatNotification notification in notifications)
                {
                    if (notification != null && notification.Type == ChatNotification.NotificationType.Connected && notification.Data == "0")
                    {
                        WaitForBusyChats();
                        if (Chats.Contains(chat))
                            try { Chats.Remove(chat); }
                            catch { }
                    }

                }

                string sJSON = Extra.oSerializer.Serialize(notifications);
                Communications.SendResponse(chat.EventHandle, Communications.DataType.JSON, sJSON);
                chat.EventHandle = null;
                notifications = null;

            }
        }

    }
}
